#!/usr/bin/env python
# -*- coding: latin-1 -*-
# ****************************************************************************
# * Software: FPDF for python                                                *
# * Version:  1.7.1                                                          *
# * Date:     2010-09-10                                                     *
# * Last update: 2012-08-16                                                  *
# * License:  LGPL v3.0                                                      *
# *                                                                          *
# * Original Author (PHP):  Olivier PLATHEY 2004-12-31                       *
# * Ported to Python 2.4 by Max (maxpat78@yahoo.it) on 2006-05               *
# * Maintainer:  Mariano Reingart (reingart@gmail.com) et al since 2008 est. *
# * NOTE: 'I' and 'D' destinations are disabled, and simply print to STDOUT  *
# ****************************************************************************

from __future__ import division, with_statement

import errno
import math
import os
import re
import struct
import sys
import tempfile
import zlib
from datetime import datetime
from functools import wraps

from .fonts import fpdf_charwidths
from .php import UTF8StringToArray, UTF8ToUTF16BE, print_r, sprintf, substr
from .py3k import (PY3K, BytesIO, Image, b, basestring, exception, hashpath,
                   pickle, unicode, urlopen)
from .ttfonts import TTFontFile

# Global variables
FPDF_VERSION = "1.7.2"
FPDF_FONT_DIR = os.path.join(os.path.dirname(__file__), "font")
FPDF_CACHE_MODE = 0  # 0 - in same folder, 1 - none, 2 - hash
FPDF_CACHE_DIR = None
SYSTEM_TTFONTS = None

PAGE_FORMATS = {
    "a3": (841.89, 1190.55),
    "a4": (595.28, 841.89),
    "a5": (420.94, 595.28),
    "letter": (612, 792),
    "legal": (612, 1008),
}


def set_global(var, val):
    globals()[var] = val


def load_cache(filename):
    """Return unpickled object, or None if cache unavailable"""
    if not filename:
        return None
    try:
        with open(filename, "rb") as fh:
            return pickle.load(fh)
    except (IOError, ValueError):  # File missing, unsupported pickle, etc
        return None


class FPDF(object):
    "PDF Generation class"

    def __init__(self, orientation="P", unit="mm", format="A4"):
        # Some checks
        self._dochecks()
        # Initialization of properties
        self.offsets = {}  # array of object offsets
        self.page = 0  # current page number
        self.n = 2  # current object number
        self.buffer = ""  # buffer holding in-memory PDF
        self.pages = {}  # array containing pages and metadata
        self.state = 0  # current document state
        self.fonts = {}  # array of used fonts
        self.font_files = {}  # array of font files
        self.diffs = {}  # array of encoding differences
        self.images = {}  # array of used images
        self.page_links = {}  # array of links in pages
        self.links = {}  # array of internal links
        self.in_footer = 0  # flag set when processing footer
        self.lastw = 0
        self.lasth = 0  # height of last cell printed
        self.font_family = ""  # current font family
        self.font_style = ""  # current font style
        self.font_size_pt = 12  # current font size in points
        self.font_stretching = 100  # current font stretching
        self.underline = 0  # underlining flag
        self.draw_color = "0 G"
        self.fill_color = "0 g"
        self.text_color = "0 g"
        self.color_flag = 0  # indicates whether fill and text colors are different
        self.ws = 0  # word spacing
        self.angle = 0
        # Standard fonts
        self.core_fonts = {
            "courier": "Courier",
            "courierB": "Courier-Bold",
            "courierI": "Courier-Oblique",
            "courierBI": "Courier-BoldOblique",
            "helvetica": "Helvetica",
            "helveticaB": "Helvetica-Bold",
            "helveticaI": "Helvetica-Oblique",
            "helveticaBI": "Helvetica-BoldOblique",
            "times": "Times-Roman",
            "timesB": "Times-Bold",
            "timesI": "Times-Italic",
            "timesBI": "Times-BoldItalic",
            "symbol": "Symbol",
            "zapfdingbats": "ZapfDingbats",
        }
        self.core_fonts_encoding = "latin-1"
        # Scale factor
        if unit == "pt":
            self.k = 1
        elif unit == "mm":
            self.k = 72 / 25.4
        elif unit == "cm":
            self.k = 72 / 2.54
        elif unit == "in":
            self.k = 72.0
        else:
            self.error("Incorrect unit: " + unit)
        # Page format
        self.fw_pt, self.fh_pt = self.get_page_format(format, self.k)
        self.dw_pt = self.fw_pt
        self.dh_pt = self.fh_pt
        self.fw = self.fw_pt / self.k
        self.fh = self.fh_pt / self.k
        # Page orientation
        orientation = orientation.lower()
        if orientation in ("p", "portrait"):
            self.def_orientation = "P"
            self.w_pt = self.fw_pt
            self.h_pt = self.fh_pt
        elif orientation in ("l", "landscape"):
            self.def_orientation = "L"
            self.w_pt = self.fh_pt
            self.h_pt = self.fw_pt
        else:
            self.error("Incorrect orientation: " + orientation)
        self.cur_orientation = self.def_orientation
        self.w = self.w_pt / self.k
        self.h = self.h_pt / self.k
        # Page margins (1 cm)
        margin = 28.35 / self.k
        self.set_margins(margin, margin)
        # Interior cell margin (1 mm)
        self.c_margin = margin / 10.0
        # line width (0.2 mm)
        self.line_width = 0.567 / self.k
        # Automatic page break
        self.set_auto_page_break(1, 2 * margin)
        # Full width display mode
        self.set_display_mode("fullwidth")
        # Enable compression
        self.set_compression(1)
        # Set default PDF version number
        self.pdf_version = "1.3"

    @staticmethod
    def get_page_format(format, k):
        "Return scale factor, page w and h size in points"
        if isinstance(format, basestring):
            format = format.lower()
            if format in PAGE_FORMATS:
                return PAGE_FORMATS[format]
            else:
                raise RuntimeError("Unknown page format: " + format)
        else:
            return (format[0] * k, format[1] * k)

    def check_page(fn):
        "Decorator to protect drawing methods"

        @wraps(fn)
        def wrapper(self, *args, **kwargs):
            if not self.page and not kwargs.get("split_only"):
                self.error("No page open, you need to call add_page() first")
            else:
                return fn(self, *args, **kwargs)

        return wrapper

    def set_margins(self, left, top, right=-1):
        "Set left, top and right margins"
        self.l_margin = left
        self.t_margin = top
        if right == -1:
            right = left
        self.r_margin = right

    def set_left_margin(self, margin):
        "Set left margin"
        self.l_margin = margin
        if self.page > 0 and self.x < margin:
            self.x = margin

    def set_top_margin(self, margin):
        "Set top margin"
        self.t_margin = margin

    def set_right_margin(self, margin):
        "Set right margin"
        self.r_margin = margin

    def set_auto_page_break(self, auto, margin=0):
        "Set auto page break mode and triggering margin"
        self.auto_page_break = auto
        self.b_margin = margin
        self.page_break_trigger = self.h - margin

    def set_display_mode(self, zoom, layout="continuous"):
        """Set display mode in viewer

        The "zoom" argument may be 'fullpage', 'fullwidth', 'real',
        'default', or a number, interpreted as a percentage."""

        if (
            zoom == "fullpage"
            or zoom == "fullwidth"
            or zoom == "real"
            or zoom == "default"
            or not isinstance(zoom, basestring)
        ):
            self.zoom_mode = zoom
        else:
            self.error("Incorrect zoom display mode: " + zoom)
        if (
            layout == "single"
            or layout == "continuous"
            or layout == "two"
            or layout == "default"
        ):
            self.layout_mode = layout
        else:
            self.error("Incorrect layout display mode: " + layout)

    def set_compression(self, compress):
        "Set page compression"
        self.compress = compress

    def set_title(self, title):
        "Title of document"
        self.title = title

    def set_subject(self, subject):
        "Subject of document"
        self.subject = subject

    def set_author(self, author):
        "Author of document"
        self.author = author

    def set_keywords(self, keywords):
        "Keywords of document"
        self.keywords = keywords

    def set_creator(self, creator):
        "Creator of document"
        self.creator = creator

    def set_doc_option(self, opt, value):
        "Set document option"
        if opt == "core_fonts_encoding":
            self.core_fonts_encoding = value
        else:
            self.error('Unknown document option "%s"' % str(opt))

    def alias_nb_pages(self, alias="{nb}"):
        "Define an alias for total number of pages"
        self.str_alias_nb_pages = alias
        return alias

    def error(self, msg):
        "Fatal error"
        raise RuntimeError("FPDF error: " + msg)

    def open(self):
        "Begin document"
        self.state = 1

    def close(self):
        "Terminate document"
        if self.state == 3:
            return
        if self.page == 0:
            self.add_page()
        # Page footer
        self.in_footer = 1
        self.footer()
        self.in_footer = 0
        # close page
        self._endpage()
        # close document
        self._enddoc()

    def add_page(self, orientation="", format="", same=False):
        "Start a new page, if same page format will be same as previous"
        if self.state == 0:
            self.open()
        family = self.font_family
        if self.underline:
            style = self.font_style + "U"
        else:
            style = self.font_style
        size = self.font_size_pt
        lw = self.line_width
        dc = self.draw_color
        fc = self.fill_color
        tc = self.text_color
        cf = self.color_flag
        stretching = self.font_stretching
        if self.page > 0:
            # Page footer
            self.in_footer = 1
            self.footer()
            self.in_footer = 0
            # close page
            self._endpage()
        # Start new page
        self._beginpage(orientation, format, same)
        # Set line cap style to square
        self._out("2 J")
        # Set line width
        self.line_width = lw
        self._out(sprintf("%.2f w", lw * self.k))
        # Set font
        if family:
            self.set_font(family, style, size)
        # Set colors
        self.draw_color = dc
        if dc != "0 G":
            self._out(dc)
        self.fill_color = fc
        if fc != "0 g":
            self._out(fc)
        self.text_color = tc
        self.color_flag = cf
        # Page header
        self.header()
        # Restore line width
        if self.line_width != lw:
            self.line_width = lw
            self._out(sprintf("%.2f w", lw * self.k))
        # Restore font
        if family:
            self.set_font(family, style, size)
        # Restore colors
        if self.draw_color != dc:
            self.draw_color = dc
            self._out(dc)
        if self.fill_color != fc:
            self.fill_color = fc
            self._out(fc)
        self.text_color = tc
        self.color_flag = cf
        # Restore stretching
        if stretching != 100:
            self.set_stretching(stretching)

    def header(self):
        "Header to be implemented in your own inherited class"
        pass

    def footer(self):
        "Footer to be implemented in your own inherited class"
        pass

    def page_no(self):
        "Get current page number"
        return self.page

    def set_draw_color(self, r, g=-1, b=-1):
        "Set color for all stroking operations"
        if (r == 0 and g == 0 and b == 0) or g == -1:
            self.draw_color = sprintf("%.3f G", r / 255.0)
        else:
            self.draw_color = sprintf(
                "%.3f %.3f %.3f RG", r / 255.0, g / 255.0, b / 255.0
            )
        if self.page > 0:
            self._out(self.draw_color)

    def set_fill_color(self, r, g=-1, b=-1):
        "Set color for all filling operations"
        if (r == 0 and g == 0 and b == 0) or g == -1:
            self.fill_color = sprintf("%.3f g", r / 255.0)
        else:
            self.fill_color = sprintf(
                "%.3f %.3f %.3f rg", r / 255.0, g / 255.0, b / 255.0
            )
        self.color_flag = self.fill_color != self.text_color
        if self.page > 0:
            self._out(self.fill_color)

    def set_text_color(self, r, g=-1, b=-1):
        "Set color for text"
        if (r == 0 and g == 0 and b == 0) or g == -1:
            self.text_color = sprintf("%.3f g", r / 255.0)
        else:
            self.text_color = sprintf(
                "%.3f %.3f %.3f rg", r / 255.0, g / 255.0, b / 255.0
            )
        self.color_flag = self.fill_color != self.text_color

    def get_string_width(self, s, normalized=False):
        "Get width of a string in the current font"
        # normalized is parameter for internal use
        s = s if normalized else self.normalize_text(s)
        cw = self.current_font["cw"]
        w = 0
        l = len(s)
        if self.unifontsubset:
            for char in s:
                char = ord(char)
                if len(cw) > char:
                    w += cw[char]  # ord(cw[2*char])<<8 + ord(cw[2*char+1])
                # elif (char>0 and char<128 and isset($cw[chr($char)])) { $w += $cw[chr($char)]; }
                elif self.current_font["desc"]["MissingWidth"]:
                    w += self.current_font["desc"]["MissingWidth"]
                # elif (isset($this->CurrentFont['MissingWidth'])) { $w += $this->CurrentFont['MissingWidth']; }
                else:
                    w += 500
        else:
            for i in range(0, l):
                w += cw.get(s[i], 0)
        if self.font_stretching != 100:
            w = w * self.font_stretching / 100.0
        return w * self.font_size / 1000.0

    def set_line_width(self, width):
        "Set line width"
        self.line_width = width
        if self.page > 0:
            self._out(sprintf("%.2f w", width * self.k))

    @check_page
    def line(self, x1, y1, x2, y2):
        "Draw a line"
        self._out(
            sprintf(
                "%.2f %.2f m %.2f %.2f l S",
                x1 * self.k,
                (self.h - y1) * self.k,
                x2 * self.k,
                (self.h - y2) * self.k,
            )
        )

    def _set_dash(self, dash_length=False, space_length=False):
        if dash_length and space_length:
            s = sprintf("[%.3f %.3f] 0 d", dash_length * self.k, space_length * self.k)
        else:
            s = "[] 0 d"
        self._out(s)

    @check_page
    def dashed_line(self, x1, y1, x2, y2, dash_length=1, space_length=1):
        """Draw a dashed line. Same interface as line() except:
        - dash_length: Length of the dash
        - space_length: Length of the space between dashes"""
        self._set_dash(dash_length, space_length)
        self.line(x1, y1, x2, y2)
        self._set_dash()

    @check_page
    def rect(self, x, y, w, h, style=""):
        "Draw a rectangle"
        if style == "F":
            op = "f"
        elif style == "FD" or style == "DF":
            op = "B"
        else:
            op = "S"
        self._out(
            sprintf(
                "%.2f %.2f %.2f %.2f re %s",
                x * self.k,
                (self.h - y) * self.k,
                w * self.k,
                -h * self.k,
                op,
            )
        )

    @check_page
    def ellipse(self, x, y, w, h, style=""):
        "Draw a ellipse"
        if style == "F":
            op = "f"
        elif style == "FD" or style == "DF":
            op = "B"
        else:
            op = "S"

        cx = x + w / 2.0
        cy = y + h / 2.0
        rx = w / 2.0
        ry = h / 2.0

        lx = 4.0 / 3.0 * (math.sqrt(2) - 1) * rx
        ly = 4.0 / 3.0 * (math.sqrt(2) - 1) * ry

        self._out(
            sprintf(
                "%.2f %.2f m %.2f %.2f %.2f %.2f %.2f %.2f c",
                (cx + rx) * self.k,
                (self.h - cy) * self.k,
                (cx + rx) * self.k,
                (self.h - (cy - ly)) * self.k,
                (cx + lx) * self.k,
                (self.h - (cy - ry)) * self.k,
                cx * self.k,
                (self.h - (cy - ry)) * self.k,
            )
        )
        self._out(
            sprintf(
                "%.2f %.2f %.2f %.2f %.2f %.2f c",
                (cx - lx) * self.k,
                (self.h - (cy - ry)) * self.k,
                (cx - rx) * self.k,
                (self.h - (cy - ly)) * self.k,
                (cx - rx) * self.k,
                (self.h - cy) * self.k,
            )
        )
        self._out(
            sprintf(
                "%.2f %.2f %.2f %.2f %.2f %.2f c",
                (cx - rx) * self.k,
                (self.h - (cy + ly)) * self.k,
                (cx - lx) * self.k,
                (self.h - (cy + ry)) * self.k,
                cx * self.k,
                (self.h - (cy + ry)) * self.k,
            )
        )
        self._out(
            sprintf(
                "%.2f %.2f %.2f %.2f %.2f %.2f c %s",
                (cx + lx) * self.k,
                (self.h - (cy + ry)) * self.k,
                (cx + rx) * self.k,
                (self.h - (cy + ly)) * self.k,
                (cx + rx) * self.k,
                (self.h - cy) * self.k,
                op,
            )
        )

    def add_font(self, family, style="", fname="", uni=False):
        "Add a TrueType or Type1 font"
        family = family.lower()
        if fname == "":
            fname = family.replace(" ", "") + style.lower() + ".pkl"
        if family == "arial":
            family = "helvetica"
        style = style.upper()
        if style == "IB":
            style = "BI"
        fontkey = family + style
        if fontkey in self.fonts:
            # Font already added!
            return
        if uni:
            global SYSTEM_TTFONTS, FPDF_CACHE_MODE, FPDF_CACHE_DIR
            if os.path.exists(fname):
                ttffilename = fname
            elif FPDF_FONT_DIR and os.path.exists(os.path.join(FPDF_FONT_DIR, fname)):
                ttffilename = os.path.join(FPDF_FONT_DIR, fname)
            elif SYSTEM_TTFONTS and os.path.exists(os.path.join(SYSTEM_TTFONTS, fname)):
                ttffilename = os.path.join(SYSTEM_TTFONTS, fname)
            else:
                raise RuntimeError("TTF Font file not found: %s" % fname)
            name = ""
            if FPDF_CACHE_MODE == 0:
                unifilename = os.path.splitext(ttffilename)[0] + ".pkl"
            elif FPDF_CACHE_MODE == 2:
                unifilename = os.path.join(
                    FPDF_CACHE_DIR, hashpath(ttffilename) + ".pkl"
                )
            else:
                unifilename = None
            font_dict = load_cache(unifilename)
            if font_dict is None:
                ttf = TTFontFile()
                ttf.getMetrics(ttffilename)
                desc = {
                    "Ascent": int(round(ttf.ascent, 0)),
                    "Descent": int(round(ttf.descent, 0)),
                    "CapHeight": int(round(ttf.capHeight, 0)),
                    "Flags": ttf.flags,
                    "FontBBox": "[%s %s %s %s]"
                    % (
                        int(round(ttf.bbox[0], 0)),
                        int(round(ttf.bbox[1], 0)),
                        int(round(ttf.bbox[2], 0)),
                        int(round(ttf.bbox[3], 0)),
                    ),
                    "ItalicAngle": int(ttf.italicAngle),
                    "StemV": int(round(ttf.stemV, 0)),
                    "MissingWidth": int(round(ttf.defaultWidth, 0)),
                }
                # Generate metrics .pkl file
                font_dict = {
                    "name": re.sub("[ ()]", "", ttf.fullName),
                    "type": "TTF",
                    "desc": desc,
                    "up": round(ttf.underlinePosition),
                    "ut": round(ttf.underlineThickness),
                    "ttffile": ttffilename,
                    "fontkey": fontkey,
                    "originalsize": os.stat(ttffilename).st_size,
                    "cw": ttf.charWidths,
                }
                if unifilename:
                    try:
                        with open(unifilename, "wb") as fh:
                            pickle.dump(font_dict, fh)
                    except IOError:
                        if not exception().errno == errno.EACCES:
                            raise  # Not a permission error.
                del ttf
            if hasattr(self, "str_alias_nb_pages"):
                sbarr = list(range(0, 57))  # include numbers in the subset!
            else:
                sbarr = list(range(0, 32))
            self.fonts[fontkey] = {
                "i": len(self.fonts) + 1,
                "type": font_dict["type"],
                "name": font_dict["name"],
                "desc": font_dict["desc"],
                "up": font_dict["up"],
                "ut": font_dict["ut"],
                "cw": font_dict["cw"],
                "ttffile": font_dict["ttffile"],
                "fontkey": fontkey,
                "subset": sbarr,
                "unifilename": unifilename,
            }
            self.font_files[fontkey] = {
                "length1": font_dict["originalsize"],
                "type": "TTF",
                "ttffile": ttffilename,
            }
            self.font_files[fname] = {"type": "TTF"}
        else:
            with open(fname, "rb") as fontfile:
                font_dict = pickle.load(fontfile)
            self.fonts[fontkey] = {"i": len(self.fonts) + 1}
            self.fonts[fontkey].update(font_dict)
            diff = font_dict.get("diff")
            if diff:
                # Search existing encodings
                d = 0
                nb = len(self.diffs)
                for i in range(1, nb + 1):
                    if self.diffs[i] == diff:
                        d = i
                        break
                if d == 0:
                    d = nb + 1
                    self.diffs[d] = diff
                self.fonts[fontkey]["diff"] = d
            filename = font_dict.get("filename")
            if filename:
                if font_dict["type"] == "TrueType":
                    originalsize = font_dict["originalsize"]
                    self.font_files[filename] = {"length1": originalsize}
                else:
                    self.font_files[filename] = {
                        "length1": font_dict["size1"],
                        "length2": font_dict["size2"],
                    }

    def set_font(self, family, style="", size=0):
        "Select a font; size given in points"
        family = family.lower()
        if family == "":
            family = self.font_family
        if family == "arial":
            family = "helvetica"
        elif family == "symbol" or family == "zapfdingbats":
            style = ""
        style = style.upper()
        if "U" in style:
            self.underline = 1
            style = style.replace("U", "")
        else:
            self.underline = 0
        if style == "IB":
            style = "BI"
        if size == 0:
            size = self.font_size_pt
        # Test if font is already selected
        if (
            self.font_family == family
            and self.font_style == style
            and self.font_size_pt == size
        ):
            return
        # Test if used for the first time
        fontkey = family + style
        if fontkey not in self.fonts:
            # Check if one of the standard fonts
            if fontkey in self.core_fonts:
                if fontkey not in fpdf_charwidths:
                    # Load metric file
                    name = os.path.join(FPDF_FONT_DIR, family)
                    if family == "times" or family == "helvetica":
                        name += style.lower()
                    with open(name + ".font") as file:
                        exec(compile(file.read(), name + ".font", "exec"))
                    if fontkey not in fpdf_charwidths:
                        self.error("Could not include font metric file for" + fontkey)
                i = len(self.fonts) + 1
                self.fonts[fontkey] = {
                    "i": i,
                    "type": "core",
                    "name": self.core_fonts[fontkey],
                    "up": -100,
                    "ut": 50,
                    "cw": fpdf_charwidths[fontkey],
                }
            else:
                self.error("Undefined font: " + family + " " + style)
        # Select it
        self.font_family = family
        self.font_style = style
        self.font_size_pt = size
        self.font_size = size / self.k
        self.current_font = self.fonts[fontkey]
        self.unifontsubset = self.fonts[fontkey]["type"] == "TTF"
        if self.page > 0:
            self._out(
                sprintf("BT /F%d %.2f Tf ET", self.current_font["i"], self.font_size_pt)
            )

    def set_font_size(self, size):
        "Set font size in points"
        if self.font_size_pt == size:
            return
        self.font_size_pt = size
        self.font_size = size / self.k
        if self.page > 0:
            self._out(
                sprintf("BT /F%d %.2f Tf ET", self.current_font["i"], self.font_size_pt)
            )

    def set_stretching(self, factor):
        "Set from stretch factor percents (default: 100.0)"
        if self.font_stretching == factor:
            return
        self.font_stretching = factor
        if self.page > 0:
            self._out(sprintf("BT %.2f Tz ET", self.font_stretching))

    def add_link(self):
        "Create a new internal link"
        n = len(self.links) + 1
        self.links[n] = (0, 0)
        return n

    def set_link(self, link, y=0, page=-1):
        "Set destination of internal link"
        if y == -1:
            y = self.y
        if page == -1:
            page = self.page
        self.links[link] = [page, y]

    def link(self, x, y, w, h, link):
        "Put a link on the page"
        if not self.page in self.page_links:
            self.page_links[self.page] = []
        self.page_links[self.page] += [
            (x * self.k, self.h_pt - y * self.k, w * self.k, h * self.k, link),
        ]

    @check_page
    def text(self, x, y, txt=""):
        "Output a string"
        txt = self.normalize_text(txt)
        if self.unifontsubset:
            txt2 = self._escape(UTF8ToUTF16BE(txt, False))
            for uni in UTF8StringToArray(txt):
                self.current_font["subset"].append(uni)
        else:
            txt2 = self._escape(txt)
        s = sprintf(
            "BT %.2f %.2f Td (%s) Tj ET", x * self.k, (self.h - y) * self.k, txt2
        )
        if self.underline and txt != "":
            s += " " + self._dounderline(x, y, txt)
        if self.color_flag:
            s = "q " + self.text_color + " " + s + " Q"
        self._out(s)

    @check_page
    def rotate(self, angle, x=None, y=None):
        if x is None:
            x = self.x
        if y is None:
            y = self.y
        if self.angle != 0:
            self._out("Q")
        self.angle = angle
        if angle != 0:
            angle *= math.pi / 180
            c = math.cos(angle)
            s = math.sin(angle)
            cx = x * self.k
            cy = (self.h - y) * self.k
            s = sprintf(
                "q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm",
                c,
                s,
                -s,
                c,
                cx,
                cy,
                -cx,
                -cy,
            )
            self._out(s)

    def accept_page_break(self):
        "Accept automatic page break or not"
        return self.auto_page_break

    @check_page
    def cell(self, w, h=0, txt="", border=0, ln=0, align="", fill=0, link=""):
        "Output a cell"
        txt = self.normalize_text(txt)
        k = self.k
        if (
            self.y + h > self.page_break_trigger
            and not self.in_footer
            and self.accept_page_break()
        ):
            # Automatic page break
            x = self.x
            ws = self.ws
            if ws > 0:
                self.ws = 0
                self._out("0 Tw")
            self.add_page(same=True)
            self.x = x
            if ws > 0:
                self.ws = ws
                self._out(sprintf("%.3f Tw", ws * k))
        if w == 0:
            w = self.w - self.r_margin - self.x
        s = ""
        if fill == 1 or border == 1:
            if fill == 1:
                if border == 1:
                    op = "B"
                else:
                    op = "f"
            else:
                op = "S"
            s = sprintf(
                "%.2f %.2f %.2f %.2f re %s ",
                self.x * k,
                (self.h - self.y) * k,
                w * k,
                -h * k,
                op,
            )
        if isinstance(border, basestring):
            x = self.x
            y = self.y
            if "L" in border:
                s += sprintf(
                    "%.2f %.2f m %.2f %.2f l S ",
                    x * k,
                    (self.h - y) * k,
                    x * k,
                    (self.h - (y + h)) * k,
                )
            if "T" in border:
                s += sprintf(
                    "%.2f %.2f m %.2f %.2f l S ",
                    x * k,
                    (self.h - y) * k,
                    (x + w) * k,
                    (self.h - y) * k,
                )
            if "R" in border:
                s += sprintf(
                    "%.2f %.2f m %.2f %.2f l S ",
                    (x + w) * k,
                    (self.h - y) * k,
                    (x + w) * k,
                    (self.h - (y + h)) * k,
                )
            if "B" in border:
                s += sprintf(
                    "%.2f %.2f m %.2f %.2f l S ",
                    x * k,
                    (self.h - (y + h)) * k,
                    (x + w) * k,
                    (self.h - (y + h)) * k,
                )
        if txt != "":
            if align == "R":
                dx = w - self.c_margin - self.get_string_width(txt, True)
            elif align == "C":
                dx = (w - self.get_string_width(txt, True)) / 2.0
            else:
                dx = self.c_margin
            if self.color_flag:
                s += "q " + self.text_color + " "

            # If multibyte, Tw has no effect - do word spacing using an adjustment before each space
            if self.ws and self.unifontsubset:
                for uni in UTF8StringToArray(txt):
                    self.current_font["subset"].append(uni)
                space = self._escape(UTF8ToUTF16BE(" ", False))
                s += sprintf(
                    "BT 0 Tw %.2F %.2F Td [",
                    (self.x + dx) * k,
                    (self.h - (self.y + 0.5 * h + 0.3 * self.font_size)) * k,
                )
                t = txt.split(" ")
                numt = len(t)
                for i in range(numt):
                    tx = t[i]
                    tx = "(" + self._escape(UTF8ToUTF16BE(tx, False)) + ")"
                    s += sprintf("%s ", tx)
                    if (i + 1) < numt:
                        adj = -(self.ws * self.k) * 1000 / self.font_size_pt
                        s += sprintf("%d(%s) ", adj, space)
                s += "] TJ"
                s += " ET"
            else:
                if self.unifontsubset:
                    txt2 = self._escape(UTF8ToUTF16BE(txt, False))
                    for uni in UTF8StringToArray(txt):
                        self.current_font["subset"].append(uni)
                else:
                    txt2 = self._escape(txt)
                s += sprintf(
                    "BT %.2f %.2f Td (%s) Tj ET",
                    (self.x + dx) * k,
                    (self.h - (self.y + 0.5 * h + 0.3 * self.font_size)) * k,
                    txt2,
                )

            if self.underline:
                s += " " + self._dounderline(
                    self.x + dx, self.y + 0.5 * h + 0.3 * self.font_size, txt
                )
            if self.color_flag:
                s += " Q"
            if link:
                self.link(
                    self.x + dx,
                    self.y + 0.5 * h - 0.5 * self.font_size,
                    self.get_string_width(txt, True),
                    self.font_size,
                    link,
                )
        if s:
            self._out(s)
        self.lasth = h
        if ln > 0:
            # Go to next line
            self.y += h
            if ln == 1:
                self.x = self.l_margin
        else:
            self.x += w

    @check_page
    def multi_cell(self, w, h, txt="", border=0, align="J", fill=0, split_only=False):
        "Output text with automatic or explicit line breaks"
        txt = self.normalize_text(txt)
        ret = []  # if split_only = True, returns splited text cells
        cw = self.current_font["cw"]
        if w == 0:
            w = self.w - self.r_margin - self.x
        wmax = (w - 2 * self.c_margin) * 1000.0 / self.font_size
        s = txt.replace("\r", "")
        nb = len(s)
        if nb > 0 and s[nb - 1] == "\n":
            nb -= 1
        b = 0
        if border:
            if border == 1:
                border = "LTRB"
                b = "LRT"
                b2 = "LR"
            else:
                b2 = ""
                if "L" in border:
                    b2 += "L"
                if "R" in border:
                    b2 += "R"
                if "T" in border:
                    b = b2 + "T"
                else:
                    b = b2
        sep = -1
        i = 0
        j = 0
        l = 0
        ns = 0
        nl = 1
        while i < nb:
            # Get next character
            c = s[i]
            if c == "\n":
                # Explicit line break
                if self.ws > 0:
                    self.ws = 0
                    if not split_only:
                        self._out("0 Tw")
                if not split_only:
                    self.cell(w, h, substr(s, j, i - j), b, 2, align, fill)
                else:
                    ret.append(substr(s, j, i - j))
                i += 1
                sep = -1
                j = i
                l = 0
                ns = 0
                nl += 1
                if border and nl == 2:
                    b = b2
                continue
            if c == " ":
                sep = i
                ls = l
                ns += 1
            if self.unifontsubset:
                l += self.get_string_width(c, True) / self.font_size * 1000.0
            else:
                l += cw.get(c, 0)
            if l > wmax:
                # Automatic line break
                if sep == -1:
                    if i == j:
                        i += 1
                    if self.ws > 0:
                        self.ws = 0
                        if not split_only:
                            self._out("0 Tw")
                    if not split_only:
                        self.cell(w, h, substr(s, j, i - j), b, 2, align, fill)
                    else:
                        ret.append(substr(s, j, i - j))
                else:
                    if align == "J":
                        if ns > 1:
                            self.ws = (wmax - ls) / 1000.0 * self.font_size / (ns - 1)
                        else:
                            self.ws = 0
                        if not split_only:
                            self._out(sprintf("%.3f Tw", self.ws * self.k))
                    if not split_only:
                        self.cell(w, h, substr(s, j, sep - j), b, 2, align, fill)
                    else:
                        ret.append(substr(s, j, sep - j))
                    i = sep + 1
                sep = -1
                j = i
                l = 0
                ns = 0
                nl += 1
                if border and nl == 2:
                    b = b2
            else:
                i += 1
        # Last chunk
        if self.ws > 0:
            self.ws = 0
            if not split_only:
                self._out("0 Tw")
        if border and "B" in border:
            b += "B"
        if not split_only:
            self.cell(w, h, substr(s, j, i - j), b, 2, align, fill)
            self.x = self.l_margin
        else:
            ret.append(substr(s, j, i - j))
        return ret

    @check_page
    def write(self, h, txt="", link=""):
        "Output text in flowing mode"
        txt = self.normalize_text(txt)
        cw = self.current_font["cw"]
        w = self.w - self.r_margin - self.x
        wmax = (w - 2 * self.c_margin) * 1000.0 / self.font_size
        s = txt.replace("\r", "")
        nb = len(s)
        sep = -1
        i = 0
        j = 0
        l = 0
        nl = 1
        while i < nb:
            # Get next character
            c = s[i]
            if c == "\n":
                # Explicit line break
                self.cell(w, h, substr(s, j, i - j), 0, 2, "", 0, link)
                i += 1
                sep = -1
                j = i
                l = 0
                if nl == 1:
                    self.x = self.l_margin
                    w = self.w - self.r_margin - self.x
                    wmax = (w - 2 * self.c_margin) * 1000.0 / self.font_size
                nl += 1
                continue
            if c == " ":
                sep = i
            if self.unifontsubset:
                l += self.get_string_width(c, True) / self.font_size * 1000.0
            else:
                l += cw.get(c, 0)
            if l > wmax:
                # Automatic line break
                if sep == -1:
                    if self.x > self.l_margin:
                        # Move to next line
                        self.x = self.l_margin
                        self.y += h
                        w = self.w - self.r_margin - self.x
                        wmax = (w - 2 * self.c_margin) * 1000.0 / self.font_size
                        i += 1
                        nl += 1
                        continue
                    if i == j:
                        i += 1
                    self.cell(w, h, substr(s, j, i - j), 0, 2, "", 0, link)
                else:
                    self.cell(w, h, substr(s, j, sep - j), 0, 2, "", 0, link)
                    i = sep + 1
                sep = -1
                j = i
                l = 0
                if nl == 1:
                    self.x = self.l_margin
                    w = self.w - self.r_margin - self.x
                    wmax = (w - 2 * self.c_margin) * 1000.0 / self.font_size
                nl += 1
            else:
                i += 1
        # Last chunk
        if i != j:
            self.cell(l / 1000.0 * self.font_size, h, substr(s, j), 0, 0, "", 0, link)

    @check_page
    def image(self, name, x=None, y=None, w=0, h=0, type="", link=""):
        "Put an image on the page"
        if not name in self.images:
            # First use of image, get info
            if type == "":
                pos = name.rfind(".")
                if not pos:
                    self.error(
                        "image file has no extension and no type was specified: " + name
                    )
                type = substr(name, pos + 1)
            type = type.lower()
            if type == "jpg" or type == "jpeg":
                info = self._parsejpg(name)
            elif type == "png":
                info = self._parsepng(name)
            else:
                # Allow for additional formats
                # maybe the image is not showing the correct extension,
                # but the header is OK,
                succeed_parsing = False
                # try all the parsing functions
                parsing_functions = [self._parsejpg, self._parsepng, self._parsegif]
                for pf in parsing_functions:
                    try:
                        info = pf(name)
                        succeed_parsing = True
                        break
                    except:
                        pass
                # last resource
                if not succeed_parsing:
                    mtd = "_parse" + type
                    if not hasattr(self, mtd):
                        self.error("Unsupported image type: " + type)
                    info = getattr(self, mtd)(name)
                mtd = "_parse" + type
                if not hasattr(self, mtd):
                    self.error("Unsupported image type: " + type)
                info = getattr(self, mtd)(name)
            info["i"] = len(self.images) + 1
            self.images[name] = info
        else:
            info = self.images[name]
        # Automatic width and height calculation if needed
        if w == 0 and h == 0:
            # Put image at 72 dpi
            w = info["w"] / self.k
            h = info["h"] / self.k
        elif w == 0:
            w = h * info["w"] / info["h"]
        elif h == 0:
            h = w * info["h"] / info["w"]
        # Flowing mode
        if y is None:
            if (
                self.y + h > self.page_break_trigger
                and not self.in_footer
                and self.accept_page_break()
            ):
                # Automatic page break
                x = self.x
                self.add_page(same=True)
                self.x = x
            y = self.y
            self.y += h
        if x is None:
            x = self.x
        self._out(
            sprintf(
                "q %.2f 0 0 %.2f %.2f %.2f cm /I%d Do Q",
                w * self.k,
                h * self.k,
                x * self.k,
                (self.h - (y + h)) * self.k,
                info["i"],
            )
        )
        if link:
            self.link(x, y, w, h, link)

    @check_page
    def ln(self, h=""):
        "Line Feed; default value is last cell height"
        self.x = self.l_margin
        if isinstance(h, basestring):
            self.y += self.lasth
        else:
            self.y += h

    def get_x(self):
        "Get x position"
        return self.x

    def set_x(self, x):
        "Set x position"
        if x >= 0:
            self.x = x
        else:
            self.x = self.w + x

    def get_y(self):
        "Get y position"
        return self.y

    def set_y(self, y):
        "Set y position and reset x"
        self.x = self.l_margin
        if y >= 0:
            self.y = y
        else:
            self.y = self.h + y

    def set_xy(self, x, y):
        "Set x and y positions"
        self.set_y(y)
        self.set_x(x)

    def output(self, name="", dest=""):
        """Output PDF to some destination

        By default the PDF is written to sys.stdout. If a name is given, the
        PDF is written to a new file. If dest='S' is given, the PDF data is
        returned as a byte string."""

        # Finish document if necessary
        if self.state < 3:
            self.close()
        dest = dest.upper()
        if dest == "":
            if name == "":
                dest = "I"
            else:
                dest = "F"
        if PY3K:
            # manage binary data as latin1 until PEP461 or similar is implemented
            buffer = self.buffer.encode("latin1")
        else:
            buffer = self.buffer
        if dest in ("I", "D"):
            # Python < 3 writes byte data transparently without "buffer"
            stdout = getattr(sys.stdout, "buffer", sys.stdout)
            stdout.write(buffer)
        elif dest == "F":
            # Save to local file
            with open(name, "wb") as f:
                f.write(buffer)
        elif dest == "S":
            # Return as a byte string
            return buffer
        else:
            self.error("Incorrect output destination: " + dest)

    def normalize_text(self, txt):
        "Check that text input is in the correct format/encoding"
        # - for TTF unicode fonts: unicode object (utf8 encoding)
        # - for built-in fonts: string instances (encoding: latin-1, cp1252)
        if not PY3K:
            if self.unifontsubset and isinstance(txt, str):
                return txt.decode("utf-8")
            elif not self.unifontsubset and isinstance(txt, unicode):
                return txt.encode(self.core_fonts_encoding)
        else:
            if not self.unifontsubset and self.core_fonts_encoding:
                return txt.encode(self.core_fonts_encoding).decode("latin-1")
        return txt

    def _dochecks(self):
        # Check for locale-related bug
        #        if(1.1==1):
        #            self.error("Don\'t alter the locale before including class file");
        # Check for decimal separator
        if sprintf("%.1f", 1.0) != "1.0":
            import locale

            locale.setlocale(locale.LC_NUMERIC, "C")

    def _getfontpath(self):
        return FPDF_FONT_DIR + "/"

    def _putpages(self):
        nb = self.page
        if hasattr(self, "str_alias_nb_pages"):
            # Replace number of pages in fonts using subsets (unicode)
            alias = UTF8ToUTF16BE(self.str_alias_nb_pages, False)
            r = UTF8ToUTF16BE(str(nb), False)
            for n in range(1, nb + 1):
                self.pages[n]["content"] = self.pages[n]["content"].replace(alias, r)
            # Now repeat for no pages in non-subset fonts
            for n in range(1, nb + 1):
                self.pages[n]["content"] = self.pages[n]["content"].replace(
                    self.str_alias_nb_pages, str(nb)
                )
        if self.def_orientation == "P":
            dw_pt = self.dw_pt
            dh_pt = self.dh_pt
        else:
            dw_pt = self.dh_pt
            dh_pt = self.dw_pt
        if self.compress:
            filter = "/Filter /FlateDecode "
        else:
            filter = ""
        for n in range(1, nb + 1):
            # Page
            self._newobj()
            self._out("<</Type /Page")
            self._out("/Parent 1 0 R")
            w_pt = self.pages[n]["w_pt"]
            h_pt = self.pages[n]["h_pt"]
            if w_pt != dw_pt or h_pt != dh_pt:
                self._out(sprintf("/MediaBox [0 0 %.2f %.2f]", w_pt, h_pt))
            self._out("/Resources 2 0 R")
            if self.page_links and n in self.page_links:
                # Links
                annots = "/Annots ["
                for pl in self.page_links[n]:
                    rect = sprintf(
                        "%.2f %.2f %.2f %.2f",
                        pl[0],
                        pl[1],
                        pl[0] + pl[2],
                        pl[1] - pl[3],
                    )
                    annots += (
                        "<</Type /Annot /Subtype /Link /Rect ["
                        + rect
                        + "] /Border [0 0 0] "
                    )
                    if isinstance(pl[4], basestring):
                        annots += (
                            "/A <</S /URI /URI " + self._textstring(pl[4]) + ">>>>"
                        )
                    else:
                        l = self.links[pl[4]]
                        if l[0] in self.orientation_changes:
                            h = w_pt
                        else:
                            h = h_pt
                        annots += sprintf(
                            "/Dest [%d 0 R /XYZ 0 %.2f null]>>",
                            1 + 2 * l[0],
                            h - l[1] * self.k,
                        )
                self._out(annots + "]")
            if self.pdf_version > "1.3":
                self._out(
                    '/Group <</Type /Group /S /Transparency"\
                    "/CS /DeviceRGB>>'
                )
            self._out("/Contents " + str(self.n + 1) + " 0 R>>")
            self._out("endobj")
            # Page content
            content = self.pages[n]["content"]
            if self.compress:
                # manage binary data as latin1 until PEP461 or similar is implemented
                p = content.encode("latin1") if PY3K else content
                p = zlib.compress(p)
            else:
                p = content
            self._newobj()
            self._out("<<" + filter + "/Length " + str(len(p)) + ">>")
            self._putstream(p)
            self._out("endobj")
        # Pages root
        self.offsets[1] = len(self.buffer)
        self._out("1 0 obj")
        self._out("<</Type /Pages")
        kids = "/Kids ["
        for i in range(0, nb):
            kids += str(3 + 2 * i) + " 0 R "
        self._out(kids + "]")
        self._out("/Count " + str(nb))
        self._out(sprintf("/MediaBox [0 0 %.2f %.2f]", dw_pt, dh_pt))
        self._out(">>")
        self._out("endobj")

    def _putfonts(self):
        nf = self.n
        for diff in self.diffs:
            # Encodings
            self._newobj()
            self._out(
                "<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ["
                + self.diffs[diff]
                + "]>>"
            )
            self._out("endobj")
        for name, info in self.font_files.items():
            if "type" in info and info["type"] != "TTF":
                # Font file embedding
                self._newobj()
                self.font_files[name]["n"] = self.n
                with open(self._getfontpath() + name, "rb", 1) as f:
                    font = f.read()
                compressed = substr(name, -2) == ".z"
                if not compressed and "length2" in info:
                    header = ord(font[0]) == 128
                    if header:
                        # Strip first binary header
                        font = substr(font, 6)
                    if header and ord(font[info["length1"]]) == 128:
                        # Strip second binary header
                        font = substr(font, 0, info["length1"]) + substr(
                            font, info["length1"] + 6
                        )
                self._out("<</Length " + str(len(font)))
                if compressed:
                    self._out("/Filter /FlateDecode")
                self._out("/Length1 " + str(info["length1"]))
                if "length2" in info:
                    self._out("/Length2 " + str(info["length2"]) + " /Length3 0")
                self._out(">>")
                self._putstream(font)
                self._out("endobj")
        flist = [(x[1]["i"], x[0], x[1]) for x in self.fonts.items()]
        flist.sort()
        for idx, k, font in flist:
            # Font objects
            self.fonts[k]["n"] = self.n + 1
            type = font["type"]
            name = font["name"]
            if type == "core":
                # Standard font
                self._newobj()
                self._out("<</Type /Font")
                self._out("/BaseFont /" + name)
                self._out("/Subtype /Type1")
                if name != "Symbol" and name != "ZapfDingbats":
                    self._out("/Encoding /WinAnsiEncoding")
                self._out(">>")
                self._out("endobj")
            elif type == "Type1" or type == "TrueType":
                # Additional Type1 or TrueType font
                self._newobj()
                self._out("<</Type /Font")
                self._out("/BaseFont /" + name)
                self._out("/Subtype /" + type)
                self._out("/FirstChar 32 /LastChar 255")
                self._out("/Widths " + str(self.n + 1) + " 0 R")
                self._out("/FontDescriptor " + str(self.n + 2) + " 0 R")
                if font["enc"]:
                    if "diff" in font:
                        self._out("/Encoding " + str(nf + font["diff"]) + " 0 R")
                    else:
                        self._out("/Encoding /WinAnsiEncoding")
                self._out(">>")
                self._out("endobj")
                # Widths
                self._newobj()
                cw = font["cw"]
                s = "["
                for i in range(32, 256):
                    # Get doesn't raise exception; returns 0 instead of None if not set
                    s += str(cw.get(chr(i)) or 0) + " "
                self._out(s + "]")
                self._out("endobj")
                # Descriptor
                self._newobj()
                s = "<</Type /FontDescriptor /FontName /" + name
                for k in (
                    "Ascent",
                    "Descent",
                    "CapHeight",
                    "Flags",
                    "FontBBox",
                    "ItalicAngle",
                    "StemV",
                    "MissingWidth",
                ):
                    s += " /%s %s" % (k, font["desc"][k])
                filename = font["file"]
                if filename:
                    s += " /FontFile"
                    if type != "Type1":
                        s += "2"
                    s += " " + str(self.font_files[filename]["n"]) + " 0 R"
                self._out(s + ">>")
                self._out("endobj")
            elif type == "TTF":
                self.fonts[k]["n"] = self.n + 1
                ttf = TTFontFile()
                fontname = "MPDFAA" + "+" + font["name"]
                subset = font["subset"]
                del subset[0]
                ttfontstream = ttf.makeSubset(font["ttffile"], subset)
                ttfontsize = len(ttfontstream)
                fontstream = zlib.compress(ttfontstream)
                codeToGlyph = ttf.codeToGlyph
                ##del codeToGlyph[0]
                # Type0 Font
                # A composite font - a font composed of other fonts, organized hierarchically
                self._newobj()
                self._out("<</Type /Font")
                self._out("/Subtype /Type0")
                self._out("/BaseFont /" + fontname + "")
                self._out("/Encoding /Identity-H")
                self._out("/DescendantFonts [" + str(self.n + 1) + " 0 R]")
                self._out("/ToUnicode " + str(self.n + 2) + " 0 R")
                self._out(">>")
                self._out("endobj")

                # CIDFontType2
                # A CIDFont whose glyph descriptions are based on TrueType font technology
                self._newobj()
                self._out("<</Type /Font")
                self._out("/Subtype /CIDFontType2")
                self._out("/BaseFont /" + fontname + "")
                self._out("/CIDSystemInfo " + str(self.n + 2) + " 0 R")
                self._out("/FontDescriptor " + str(self.n + 3) + " 0 R")
                if font["desc"].get("MissingWidth"):
                    self._out("/DW %d" % font["desc"]["MissingWidth"])
                self._putTTfontwidths(font, ttf.maxUni)
                self._out("/CIDToGIDMap " + str(self.n + 4) + " 0 R")
                self._out(">>")
                self._out("endobj")

                # ToUnicode
                self._newobj()
                toUni = (
                    "/CIDInit /ProcSet findresource begin\n"
                    "12 dict begin\n"
                    "begincmap\n"
                    "/CIDSystemInfo\n"
                    "<</Registry (Adobe)\n"
                    "/Ordering (UCS)\n"
                    "/Supplement 0\n"
                    ">> def\n"
                    "/CMapName /Adobe-Identity-UCS def\n"
                    "/CMapType 2 def\n"
                    "1 begincodespacerange\n"
                    "<0000> <FFFF>\n"
                    "endcodespacerange\n"
                    "1 beginbfrange\n"
                    "<0000> <FFFF> <0000>\n"
                    "endbfrange\n"
                    "endcmap\n"
                    "CMapName currentdict /CMap defineresource pop\n"
                    "end\n"
                    "end"
                )
                self._out("<</Length " + str(len(toUni)) + ">>")
                self._putstream(toUni)
                self._out("endobj")

                # CIDSystemInfo dictionary
                self._newobj()
                self._out("<</Registry (Adobe)")
                self._out("/Ordering (UCS)")
                self._out("/Supplement 0")
                self._out(">>")
                self._out("endobj")

                # Font descriptor
                self._newobj()
                self._out("<</Type /FontDescriptor")
                self._out("/FontName /" + fontname)
                for kd in (
                    "Ascent",
                    "Descent",
                    "CapHeight",
                    "Flags",
                    "FontBBox",
                    "ItalicAngle",
                    "StemV",
                    "MissingWidth",
                ):
                    v = font["desc"][kd]
                    if kd == "Flags":
                        v = v | 4
                        v = v & ~32
                        # SYMBOLIC font flag
                    self._out(" /%s %s" % (kd, v))
                self._out("/FontFile2 " + str(self.n + 2) + " 0 R")
                self._out(">>")
                self._out("endobj")

                # Embed CIDToGIDMap
                # A specification of the mapping from CIDs to glyph indices
                cidtogidmap = ""
                cidtogidmap = ["\x00"] * 256 * 256 * 2
                for cc, glyph in codeToGlyph.items():
                    cidtogidmap[cc * 2] = chr(glyph >> 8)
                    cidtogidmap[cc * 2 + 1] = chr(glyph & 0xFF)
                cidtogidmap = "".join(cidtogidmap)
                if PY3K:
                    # manage binary data as latin1 until PEP461-like function is implemented
                    cidtogidmap = cidtogidmap.encode("latin1")
                cidtogidmap = zlib.compress(cidtogidmap)
                self._newobj()
                self._out("<</Length " + str(len(cidtogidmap)) + "")
                self._out("/Filter /FlateDecode")
                self._out(">>")
                self._putstream(cidtogidmap)
                self._out("endobj")

                # Font file
                self._newobj()
                self._out("<</Length " + str(len(fontstream)))
                self._out("/Filter /FlateDecode")
                self._out("/Length1 " + str(ttfontsize))
                self._out(">>")
                self._putstream(fontstream)
                self._out("endobj")
                del ttf
            else:
                # Allow for additional types
                mtd = "_put" + type.lower()
                if not method_exists(self, mtd):
                    self.error("Unsupported font type: " + type)
                self.mtd(font)

    def _putTTfontwidths(self, font, maxUni):
        if font["unifilename"]:
            cw127fname = os.path.splitext(font["unifilename"])[0] + ".cw127.pkl"
        else:
            cw127fname = None
        font_dict = load_cache(cw127fname)
        if font_dict is None:
            rangeid = 0
            range_ = {}
            range_interval = {}
            prevcid = -2
            prevwidth = -1
            interval = False
            startcid = 1
        else:
            rangeid = font_dict["rangeid"]
            range_ = font_dict["range"]
            prevcid = font_dict["prevcid"]
            prevwidth = font_dict["prevwidth"]
            interval = font_dict["interval"]
            range_interval = font_dict["range_interval"]
            startcid = 128
        cwlen = maxUni + 1

        # for each character
        subset = set(font["subset"])
        for cid in range(startcid, cwlen):
            if cid == 128 and cw127fname and not os.path.exists(cw127fname):
                try:
                    with open(cw127fname, "wb") as fh:
                        font_dict = {}
                        font_dict["rangeid"] = rangeid
                        font_dict["prevcid"] = prevcid
                        font_dict["prevwidth"] = prevwidth
                        font_dict["interval"] = interval
                        font_dict["range_interval"] = range_interval
                        font_dict["range"] = range_
                        pickle.dump(font_dict, fh)
                except IOError:
                    if not exception().errno == errno.EACCES:
                        raise  # Not a permission error.
            if cid > 255 and (cid not in subset):  #
                continue
            width = font["cw"][cid]
            if width == 0:
                continue
            if width == 65535:
                width = 0
            if "dw" not in font or (font["dw"] and width != font["dw"]):
                if cid == (prevcid + 1):
                    if width == prevwidth:
                        if width == range_[rangeid][0]:
                            range_.setdefault(rangeid, []).append(width)
                        else:
                            range_[rangeid].pop()
                            # new range
                            rangeid = prevcid
                            range_[rangeid] = [prevwidth, width]
                        interval = True
                        range_interval[rangeid] = True
                    else:
                        if interval:
                            # new range
                            rangeid = cid
                            range_[rangeid] = [width]
                        else:
                            range_[rangeid].append(width)
                        interval = False
                else:
                    rangeid = cid
                    range_[rangeid] = [width]
                    interval = False
                prevcid = cid
                prevwidth = width
        prevk = -1
        nextk = -1
        prevint = False
        for k, ws in sorted(range_.items()):
            cws = len(ws)
            if k == nextk and not prevint and (not k in range_interval or cws < 3):
                if k in range_interval:
                    del range_interval[k]
                range_[prevk] = range_[prevk] + range_[k]
                del range_[k]
            else:
                prevk = k
            nextk = k + cws
            if k in range_interval:
                prevint = cws > 3
                del range_interval[k]
                nextk -= 1
            else:
                prevint = False
        w = []
        for k, ws in sorted(range_.items()):
            if len(set(ws)) == 1:
                w.append(" %s %s %s" % (k, k + len(ws) - 1, ws[0]))
            else:
                w.append(" %s [ %s ]\n" % (k, " ".join([str(int(h)) for h in ws])))  ##
        self._out("/W [%s]" % "".join(w))

    def _putimages(self):
        filter = ""
        if self.compress:
            filter = "/Filter /FlateDecode "
        i = [(x[1]["i"], x[1]) for x in self.images.items()]
        i.sort()
        for idx, info in i:
            self._putimage(info)
            del info["data"]
            if "smask" in info:
                del info["smask"]

    def _putimage(self, info):
        if "data" in info:
            self._newobj()
            info["n"] = self.n
            self._out("<</Type /XObject")
            self._out("/Subtype /Image")
            self._out("/Width " + str(info["w"]))
            self._out("/Height " + str(info["h"]))
            if info["cs"] == "Indexed":
                self._out(
                    "/ColorSpace [/Indexed /DeviceRGB "
                    + str(len(info["pal"]) // 3 - 1)
                    + " "
                    + str(self.n + 1)
                    + " 0 R]"
                )
            else:
                self._out("/ColorSpace /" + info["cs"])
                if info["cs"] == "DeviceCMYK":
                    self._out("/Decode [1 0 1 0 1 0 1 0]")
            self._out("/BitsPerComponent " + str(info["bpc"]))
            if "f" in info:
                self._out("/Filter /" + info["f"])
            if "dp" in info:
                self._out("/DecodeParms <<" + info["dp"] + ">>")
            if "trns" in info and isinstance(info["trns"], list):
                trns = ""
                for i in range(0, len(info["trns"])):
                    trns += str(info["trns"][i]) + " " + str(info["trns"][i]) + " "
                self._out("/Mask [" + trns + "]")
            if "smask" in info:
                self._out("/SMask " + str(self.n + 1) + " 0 R")
            self._out("/Length " + str(len(info["data"])) + ">>")
            self._putstream(info["data"])
            self._out("endobj")
            # Soft mask
            if "smask" in info:
                dp = "/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns " + str(
                    info["w"]
                )
                smask = {
                    "w": info["w"],
                    "h": info["h"],
                    "cs": "DeviceGray",
                    "bpc": 8,
                    "f": info["f"],
                    "dp": dp,
                    "data": info["smask"],
                }
                self._putimage(smask)
            # Palette
            if info["cs"] == "Indexed":
                self._newobj()
                filter = self.compress and "/Filter /FlateDecode " or ""
                if self.compress:
                    pal = zlib.compress(info["pal"])
                else:
                    pal = info["pal"]
                self._out("<<" + filter + "/Length " + str(len(pal)) + ">>")
                self._putstream(pal)
                self._out("endobj")

    def _putxobjectdict(self):
        i = [(x["i"], x["n"]) for x in self.images.values()]
        i.sort()
        for idx, n in i:
            self._out("/I" + str(idx) + " " + str(n) + " 0 R")

    def _putresourcedict(self):
        self._out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
        self._out("/Font <<")
        f = [(x["i"], x["n"]) for x in self.fonts.values()]
        f.sort()
        for idx, n in f:
            self._out("/F" + str(idx) + " " + str(n) + " 0 R")
        self._out(">>")
        self._out("/XObject <<")
        self._putxobjectdict()
        self._out(">>")

    def _putresources(self):
        self._putfonts()
        self._putimages()
        # Resource dictionary
        self.offsets[2] = len(self.buffer)
        self._out("2 0 obj")
        self._out("<<")
        self._putresourcedict()
        self._out(">>")
        self._out("endobj")

    def _putinfo(self):
        self._out(
            "/Producer "
            + self._textstring(
                "PyFPDF " + FPDF_VERSION + " http://pyfpdf.googlecode.com/"
            )
        )
        if hasattr(self, "title"):
            self._out("/Title " + self._textstring(self.title))
        if hasattr(self, "subject"):
            self._out("/Subject " + self._textstring(self.subject))
        if hasattr(self, "author"):
            self._out("/Author " + self._textstring(self.author))
        if hasattr(self, "keywords"):
            self._out("/Keywords " + self._textstring(self.keywords))
        if hasattr(self, "creator"):
            self._out("/Creator " + self._textstring(self.creator))
        self._out(
            "/CreationDate "
            + self._textstring("D:" + datetime.now().strftime("%Y%m%d%H%M%S"))
        )

    def _putcatalog(self):
        self._out("/Type /Catalog")
        self._out("/Pages 1 0 R")
        if self.zoom_mode == "fullpage":
            self._out("/OpenAction [3 0 R /Fit]")
        elif self.zoom_mode == "fullwidth":
            self._out("/OpenAction [3 0 R /FitH null]")
        elif self.zoom_mode == "real":
            self._out("/OpenAction [3 0 R /XYZ null null 1]")
        elif not isinstance(self.zoom_mode, basestring):
            self._out(
                sprintf("/OpenAction [3 0 R /XYZ null null %s]", self.zoom_mode / 100)
            )
        if self.layout_mode == "single":
            self._out("/PageLayout /SinglePage")
        elif self.layout_mode == "continuous":
            self._out("/PageLayout /OneColumn")
        elif self.layout_mode == "two":
            self._out("/PageLayout /TwoColumnLeft")

    def _putheader(self):
        self._out("%PDF-" + self.pdf_version)

    def _puttrailer(self):
        self._out("/Size " + str(self.n + 1))
        self._out("/Root " + str(self.n) + " 0 R")
        self._out("/Info " + str(self.n - 1) + " 0 R")

    def _enddoc(self):
        self._putheader()
        self._putpages()
        self._putresources()
        # Info
        self._newobj()
        self._out("<<")
        self._putinfo()
        self._out(">>")
        self._out("endobj")
        # Catalog
        self._newobj()
        self._out("<<")
        self._putcatalog()
        self._out(">>")
        self._out("endobj")
        # Cross-ref
        o = len(self.buffer)
        self._out("xref")
        self._out("0 " + (str(self.n + 1)))
        self._out("0000000000 65535 f ")
        for i in range(1, self.n + 1):
            self._out(sprintf("%010d 00000 n ", self.offsets[i]))
        # Trailer
        self._out("trailer")
        self._out("<<")
        self._puttrailer()
        self._out(">>")
        self._out("startxref")
        self._out(o)
        self._out("%%EOF")
        self.state = 3

    def _beginpage(self, orientation, format, same):
        self.page += 1
        self.pages[self.page] = {"content": ""}
        self.state = 2
        self.x = self.l_margin
        self.y = self.t_margin
        self.font_family = ""
        self.font_stretching = 100
        if not same:
            # Page format
            if format:
                # Change page format
                self.fw_pt, self.fh_pt = self.get_page_format(format, self.k)
            else:
                # Set to default format
                self.fw_pt = self.dw_pt
                self.fh_pt = self.dh_pt
            self.fw = self.fw_pt / self.k
            self.fh = self.fh_pt / self.k
            # Page orientation
            if not orientation:
                orientation = self.def_orientation
            else:
                orientation = orientation[0].upper()
            if orientation == "P":
                self.w_pt = self.fw_pt
                self.h_pt = self.fh_pt
            else:
                self.w_pt = self.fh_pt
                self.h_pt = self.fw_pt
            self.w = self.w_pt / self.k
            self.h = self.h_pt / self.k
            self.cur_orientation = orientation
            self.page_break_trigger = self.h - self.b_margin
            self.cur_orientation = orientation
        self.pages[self.page]["w_pt"] = self.w_pt
        self.pages[self.page]["h_pt"] = self.h_pt

    def _endpage(self):
        # End of page contents
        self.state = 1

    def _newobj(self):
        # Begin a new object
        self.n += 1
        self.offsets[self.n] = len(self.buffer)
        self._out(str(self.n) + " 0 obj")

    def _dounderline(self, x, y, txt):
        # Underline text
        up = self.current_font["up"]
        ut = self.current_font["ut"]
        w = self.get_string_width(txt, True) + self.ws * txt.count(" ")
        return sprintf(
            "%.2f %.2f %.2f %.2f re f",
            x * self.k,
            (self.h - (y - up / 1000.0 * self.font_size)) * self.k,
            w * self.k,
            -ut / 1000.0 * self.font_size_pt,
        )

    def load_resource(self, reason, filename):
        "Load external file"
        # by default loading from network is allowed for all images
        if reason == "image":
            if filename.startswith("http://") or filename.startswith("https://"):
                f = BytesIO(urlopen(filename).read())
            else:
                f = open(filename, "rb")
            return f
        else:
            self.error('Unknown resource loading reason "%s"' % reason)

    def _parsejpg(self, filename):
        # Extract info from a JPEG file
        f = None
        try:
            f = self.load_resource("image", filename)
            while True:
                markerHigh, markerLow = struct.unpack("BB", f.read(2))
                if markerHigh != 0xFF or markerLow < 0xC0:
                    raise SyntaxError("No JPEG marker found")
                elif markerLow == 0xDA:  # SOS
                    raise SyntaxError("No JPEG SOF marker found")
                elif (
                    markerLow == 0xC8
                    or (markerLow >= 0xD0 and markerLow <= 0xD9)  # JPG
                    or (markerLow >= 0xF0 and markerLow <= 0xFD)  # RSTx
                ):  # JPGx
                    pass
                else:
                    (dataSize,) = struct.unpack(">H", f.read(2))
                    data = f.read(dataSize - 2) if dataSize > 2 else ""
                    if (
                        (markerLow >= 0xC0 and markerLow <= 0xC3)
                        or (markerLow >= 0xC5 and markerLow <= 0xC7)  # SOF0 - SOF3
                        or (markerLow >= 0xC9 and markerLow <= 0xCB)  # SOF4 - SOF7
                        or (markerLow >= 0xCD and markerLow <= 0xCF)  # SOF9 - SOF11
                    ):  # SOF13 - SOF15
                        bpc, height, width, layers = struct.unpack_from(">BHHB", data)
                        colspace = (
                            "DeviceRGB"
                            if layers == 3
                            else ("DeviceCMYK" if layers == 4 else "DeviceGray")
                        )
                        break
        except Exception:
            if f:
                f.close()
            self.error(
                "Missing or incorrect image file: %s. error: %s"
                % (filename, str(exception()))
            )

        with f:
            # Read whole file from the start
            f.seek(0)
            data = f.read()
        return {
            "w": width,
            "h": height,
            "cs": colspace,
            "bpc": bpc,
            "f": "DCTDecode",
            "data": data,
        }

    def _parsegif(self, filename):
        # Extract info from a GIF file (via PNG conversion)
        if Image is None:
            self.error("PIL is required for GIF support")
        try:
            im = Image.open(filename)
        except Exception:
            self.error(
                "Missing or incorrect image file: %s. error: %s"
                % (filename, str(exception()))
            )
        else:
            # Use temporary file
            with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as f:
                tmp = f.name
            if "transparency" in im.info:
                im.save(tmp, transparency=im.info["transparency"])
            else:
                im.save(tmp)
            info = self._parsepng(tmp)
            os.unlink(tmp)
        return info

    def _parsepng(self, filename):
        # Extract info from a PNG file
        f = self.load_resource("image", filename)
        # Check signature
        magic = f.read(8).decode("latin1")
        signature = "\x89" + "PNG" + "\r" + "\n" + "\x1a" + "\n"
        if not PY3K:
            signature = signature.decode("latin1")
        if magic != signature:
            self.error("Not a PNG file: " + filename)
        # Read header chunk
        f.read(4)
        chunk = f.read(4).decode("latin1")
        if chunk != "IHDR":
            self.error("Incorrect PNG file: " + filename)
        w = self._freadint(f)
        h = self._freadint(f)
        bpc = ord(f.read(1))
        if bpc > 8:
            self.error("16-bit depth not supported: " + filename)
        ct = ord(f.read(1))
        if ct == 0 or ct == 4:
            colspace = "DeviceGray"
        elif ct == 2 or ct == 6:
            colspace = "DeviceRGB"
        elif ct == 3:
            colspace = "Indexed"
        else:
            self.error("Unknown color type: " + filename)
        if ord(f.read(1)) != 0:
            self.error("Unknown compression method: " + filename)
        if ord(f.read(1)) != 0:
            self.error("Unknown filter method: " + filename)
        if ord(f.read(1)) != 0:
            self.error("Interlacing not supported: " + filename)
        f.read(4)
        dp = "/Predictor 15 /Colors "
        if colspace == "DeviceRGB":
            dp += "3"
        else:
            dp += "1"
        dp += " /BitsPerComponent " + str(bpc) + " /Columns " + str(w) + ""
        # Scan chunks looking for palette, transparency and image data
        pal = ""
        trns = ""
        data = bytes() if PY3K else str()
        n = 1
        while n != None:
            n = self._freadint(f)
            type = f.read(4).decode("latin1")
            if type == "PLTE":
                # Read palette
                pal = f.read(n)
                f.read(4)
            elif type == "tRNS":
                # Read transparency info
                t = f.read(n)
                if ct == 0:
                    trns = [
                        ord(substr(t, 1, 1)),
                    ]
                elif ct == 2:
                    trns = [
                        ord(substr(t, 1, 1)),
                        ord(substr(t, 3, 1)),
                        ord(substr(t, 5, 1)),
                    ]
                else:
                    pos = t.find("\x00".encode("latin1"))
                    if pos != -1:
                        trns = [
                            pos,
                        ]
                f.read(4)
            elif type == "IDAT":
                # Read image data block
                data += f.read(n)
                f.read(4)
            elif type == "IEND":
                break
            else:
                f.read(n + 4)
        if colspace == "Indexed" and not pal:
            self.error("Missing palette in " + filename)
        f.close()
        info = {
            "w": w,
            "h": h,
            "cs": colspace,
            "bpc": bpc,
            "f": "FlateDecode",
            "dp": dp,
            "pal": pal,
            "trns": trns,
        }
        if ct >= 4:
            # Extract alpha channel
            data = zlib.decompress(data)
            color = b("")
            alpha = b("")
            if ct == 4:
                # Gray image
                length = 2 * w
                for i in range(h):
                    pos = (1 + length) * i
                    color += b(data[pos])
                    alpha += b(data[pos])
                    line = substr(data, pos + 1, length)
                    re_c = re.compile(r"(.).".encode("ascii"), flags=re.DOTALL)
                    re_a = re.compile(r".(.)".encode("ascii"), flags=re.DOTALL)
                    color += re_c.sub(lambda m: m.group(1), line)
                    alpha += re_a.sub(lambda m: m.group(1), line)
            else:
                # RGB image
                length = 4 * w
                for i in range(h):
                    pos = (1 + length) * i
                    color += b(data[pos])
                    alpha += b(data[pos])
                    line = substr(data, pos + 1, length)
                    re_c = re.compile(r"(...).".encode("ascii"), flags=re.DOTALL)
                    re_a = re.compile(r"...(.)".encode("ascii"), flags=re.DOTALL)
                    color += re_c.sub(lambda m: m.group(1), line)
                    alpha += re_a.sub(lambda m: m.group(1), line)
            del data
            data = zlib.compress(color)
            info["smask"] = zlib.compress(alpha)
            if self.pdf_version < "1.4":
                self.pdf_version = "1.4"
        info["data"] = data
        return info

    def _freadint(self, f):
        # Read a 4-byte integer from file
        try:
            return struct.unpack(">I", f.read(4))[0]
        except:
            return None

    def _textstring(self, s):
        # Format a text string
        return "(" + self._escape(s) + ")"

    def _escape(self, s):
        # Add \ before \, ( and )
        return (
            s.replace("\\", "\\\\")
            .replace(")", "\\)")
            .replace("(", "\\(")
            .replace("\r", "\\r")
        )

    def _putstream(self, s):
        self._out("stream")
        self._out(s)
        self._out("endstream")

    def _out(self, s):
        # Add a line to the document
        if PY3K and isinstance(s, bytes):
            # manage binary data as latin1 until PEP461-like function is implemented
            s = s.decode("latin1")
        elif not PY3K and isinstance(s, unicode):
            s = s.encode("latin1")  # default encoding (font name and similar)
        elif not isinstance(s, basestring):
            s = str(s)
        if self.state == 2:
            self.pages[self.page]["content"] += s + "\n"
        else:
            self.buffer += s + "\n"

    @check_page
    def interleaved2of5(self, txt, x, y, w=1.0, h=10.0):
        "Barcode I2of5 (numeric), adds a 0 if odd lenght"
        narrow = w / 3.0
        wide = w

        # wide/narrow codes for the digits
        bar_char = {
            "0": "nnwwn",
            "1": "wnnnw",
            "2": "nwnnw",
            "3": "wwnnn",
            "4": "nnwnw",
            "5": "wnwnn",
            "6": "nwwnn",
            "7": "nnnww",
            "8": "wnnwn",
            "9": "nwnwn",
            "A": "nn",
            "Z": "wn",
        }

        self.set_fill_color(0)
        code = txt
        # add leading zero if code-length is odd
        if len(code) % 2 != 0:
            code = "0" + code

        # add start and stop codes
        code = "AA" + code.lower() + "ZA"

        for i in range(0, len(code), 2):
            # choose next pair of digits
            char_bar = code[i]
            char_space = code[i + 1]
            # check whether it is a valid digit
            if not char_bar in bar_char.keys():
                raise RuntimeError('Char "%s" invalid for I25: ' % char_bar)
            if not char_space in bar_char.keys():
                raise RuntimeError('Char "%s" invalid for I25: ' % char_space)

            # create a wide/narrow-seq (first digit=bars, second digit=spaces)
            seq = ""
            for s in range(0, len(bar_char[char_bar])):
                seq += bar_char[char_bar][s] + bar_char[char_space][s]

            for bar in range(0, len(seq)):
                # set line_width depending on value
                if seq[bar] == "n":
                    line_width = narrow
                else:
                    line_width = wide

                # draw every second value, the other is represented by space
                if bar % 2 == 0:
                    self.rect(x, y, line_width, h, "F")

                x += line_width

    @check_page
    def code39(self, txt, x, y, w=1.5, h=5.0):
        """Barcode 3of9"""
        dim = {"w": w, "n": w / 3.0}
        chars = {
            "0": "nnnwwnwnn",
            "1": "wnnwnnnnw",
            "2": "nnwwnnnnw",
            "3": "wnwwnnnnn",
            "4": "nnnwwnnnw",
            "5": "wnnwwnnnn",
            "6": "nnwwwnnnn",
            "7": "nnnwnnwnw",
            "8": "wnnwnnwnn",
            "9": "nnwwnnwnn",
            "A": "wnnnnwnnw",
            "B": "nnwnnwnnw",
            "C": "wnwnnwnnn",
            "D": "nnnnwwnnw",
            "E": "wnnnwwnnn",
            "F": "nnwnwwnnn",
            "G": "nnnnnwwnw",
            "H": "wnnnnwwnn",
            "I": "nnwnnwwnn",
            "J": "nnnnwwwnn",
            "K": "wnnnnnnww",
            "L": "nnwnnnnww",
            "M": "wnwnnnnwn",
            "N": "nnnnwnnww",
            "O": "wnnnwnnwn",
            "P": "nnwnwnnwn",
            "Q": "nnnnnnwww",
            "R": "wnnnnnwwn",
            "S": "nnwnnnwwn",
            "T": "nnnnwnwwn",
            "U": "wwnnnnnnw",
            "V": "nwwnnnnnw",
            "W": "wwwnnnnnn",
            "X": "nwnnwnnnw",
            "Y": "wwnnwnnnn",
            "Z": "nwwnwnnnn",
            "-": "nwnnnnwnw",
            ".": "wwnnnnwnn",
            " ": "nwwnnnwnn",
            "*": "nwnnwnwnn",
            "$": "nwnwnwnnn",
            "/": "nwnwnnnwn",
            "+": "nwnnnwnwn",
            "%": "nnnwnwnwn",
        }
        self.set_fill_color(0)
        for c in txt.upper():
            if c not in chars:
                raise RuntimeError('Invalid char "%s" for Code39' % c)
            for i, d in enumerate(chars[c]):
                if i % 2 == 0:
                    self.rect(x, y, dim[d], h, "F")
                x += dim[d]
            x += dim["n"]
